/**
 * @file router
 */

var debug = require('debug')('struts:router');
var Route = require('./route')
var flatten = require('array-flatten');
var Layer = require('./layer');
var methods = require('methods');
var parseurl = require('parseurl');

var slice = Array.prototype.slice;
var toString = Object.prototype.toString;

/**
 * Router
 *
 * @param {Object} options [description]
 */
function Router(options) {
    options = options || {};
    this.params = {};
    this._params = [];
    this.stack = [];
    this.strict = options.strict;
}


Router.prototype.handle = function (req, res, out) {
    var me = this;
    debug('dispatching %s %S', req.method, req.url);

    var search = 1 + req.url.indexOf('?');
    var pathLength = search ? search - 1 : req.url.length;
    var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathLength).indexOf('://');
    var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : ''; // get http://
    var index = 0;
    var options = {};
    var paramCalled = {};
    var removed = '';

    var stack = this.stack;

    var params = req.params;
    var parentUrl = req.baseUrl || '';

    var done = restore(out, req, 'baseUrl', 'next', 'params');

    req.next = next;

    next();

    function next(err) {
        var layerError = err === 'route' ? null : err;

        if (index >= stack.length) {
            setImmediate(done, layerError);
            return;
        }

        // restore altered req.url
        if (removed.length !== 0) {
            req.baseUrl = parentUrl;
            req.url = protohost + removed + req.url.substr(protohost.length);
            removed = '';
        }
        // get path of req
        var path = getPathname(req);

        if (path == null) {
            return done(layerError);
        }

        var layer;
        var match;
        var route;

        // 找到某个layer
        while (match !== true && index < stack.length) {
            layer = stack[index++];
            match = matchLayer(layer, path); // 可能返回错误
            route = layer.route;

            if (typeof match !== 'boolean') {
                layerError = layerError || match
            }

            if (match !== true) {
                continue;
            }

            if (!route) {
                continue;
            }

            if (layerError) {
                match = false;
                continue;
            }

            var method = req.method;
            var hasMethod = route.handleMethod(method);

            if (!hasMethod && method !== 'HEAD') {
                match = false;
                continue;
            }
        }

        if (match !== true) {
            return done(layerError);
        }

        if (route) {
            req.route = route;
        }

        req.params = layer.params;

        me.processParams(layer, paramCalled, req, res, function (err) {
            if (err) {
                return next(layerError || err);
            }

            if (route) {
                return layer.handleRequest(req, res, next);
            }

            // 不知道有啥用
            trimPrefix(layer, layerError, path);
        });
    }

    function trimPrefix(layer, layerError, path) {
        var layerPath = layer.path;
        var c = path[layerPath.length];

        if (c && '/' !== c && '.' !== c) {
            return next(layerError);
        }

        if (layerPath.length !== 0) {
            debug('trim prefix (%s) from url %s', layerPath, req.url);
            removed = layerPath;
            req.url = protohost + req.url.substr(protohost.length + removed.length);

            // Setup base URL (no trailing slash)
            req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
            ? removed.substring(0, removed.length - 1)
            : removed);
        }

        debug('%s %s : %s', layer.name, layerPath, req.originalUrl);

        if (layerError) {
            layer.handleError(layerError, req, res, next);
        }
        else {
            layer.handleRequest(req, res, next);
        }
    }
};

/**
 * process params
 *
 * @param  {Layer}   layer
 * @param  {Object}   paramCalled
 * @param  {Object}   req
 * @param  {Object}   res
 * @param  {Function} fn
 * @return {[type]}
 */
Router.prototype.processParams = function(layer, paramCalled, req, res, done) {
    var params = this.params;
    var keys = layer.keys;
    console.log('keys', keys);
    if (!keys || keys.length === 0) {
        return done();
    }

    var i = 0;
    var name;
    var paramIndex = 0;
    var key;
    var paramVal;
    var paramCallbacks;
    var paramCalled;

    console.log('processParams doing');

};


/**
 * use middleware
 *
 * @param  {Function} fn middleware
 * @return {Router}
 */
Router.prototype.use = function (fn) {
    var offset = 0;
    var path = '/';

    if (typeof fn !== 'function') {
        var arg = fn;
        while (Array.isArray(arg) && arg.length !== 0) {
            arg = arg[0];
        }

        // app.use('/', fn);
        if (typeof arg !== 'function') {
            offset = 1;
            path = fn;
        }
    }

    var callbacks = flatten(slice.call(arguments, offset));

    if (callbacks.length === 0) {
        throw new TypeError('Router.use() requires middleware functions');
    }

    for (var i = 0; i < callbacks.length; i++) {
        var fn = callbacks[i];
        if (typeof fn !== 'function') {
            throw new TypeError('Router.use() requires middleware function');
        }

        debug('use %s %s', path, fn.name || '<anonymous>');

        var layer = new Layer(path, {
            end: false
        }, fn);

        layer.route = undefined;

        this.stack.push(layer);
    }

    return this;

};

/**
 * get new router
 *
 * @param  {string} path
 * @return {Router}
 */
Router.prototype.route = function (path) {
    var route = new Route(path);
    var layer = new Layer(path, {
        end: true
    }, route.dispath.bind(route));

    layer.route = route;

    this.stack.push(layer);
    return route;
};


methods.concat('all').forEach(function (method) {
    Router.prototype[method] = function (path) {
        var route = this.route(path);
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    };
});



// helper
function matchLayer(layer, path) {
    try {
      return layer.match(path);
    } catch (err) {
        return err;
    }
}

function restore(fn, obj) {
    var keys = new Array(arguments.length - 2);
    var vals = new Array(arguments.length - 2);

    for (var i = 0; i < keys.length; i++) {
        keys[i] = arguments[i+2];
        vals[i] = obj[keys[i]];
    }

    return function (err) {
        for (var i = 0; i < keys.length; i++) {
            obj[keys[i]] = vals[i];
        }

        return fn.apply(this, arguments);
    }
}


function getPathname(req) {
    try {
      return parseurl(req).pathname;
    }
    catch (err) {
        return undefined;
    }
}


module.exports = Router;
